﻿using System;
using System.Collections.Generic;
using System.Data;
using JTLeigh.Commerce.Spencer.Core.Common;
using JTLeigh.Commerce.Spencer.Core.Common.Services;
using JTLeigh.Commerce.Spencer.Core.Data.Services;
using JTLeigh.Commerce.Spencer.Features.ProductImages.Common;
using JTLeigh.Commerce.Spencer.Features.ProductImages.Common.Entities;
using Microsoft.Practices.Unity;

namespace JTLeigh.Commerce.Spencer.Features.ProductImages.Data.Providers
{

	/// <summary>
	/// An <see cref="IProductImageProvider"/> implementation that retrieves product images from 
	/// the database.
	/// </summary>
	public class ProductImageProvider : IProductImageProvider
	{

		#region Inner Classes and Types

		internal static class StoredProcedure
		{

			public static class GetProductImages
			{

				public const string CommandName = "GetProductImages";

				public static class ParameterName
				{
					public const string TenantId = "@TenantId";
                    public const string CatalogId = "@CatalogId";
                    public const string ProductId = "@ProductId";
                }

				public static class ColumnName
				{

					public static class ProductImage
					{
						public const string ImageId = "ImageId";
						public const string ViewId = "ViewId";
						public const string DefaultAlternativeText = "DefaultAlternativeText";
						public const string DefaultUsagePermissionText = "DefaultUsagePermissionText";
					}

					public static class ProductImageLocalization
					{
						public const string CultureId = "CultureId";
						public const string AlternativeText = "AlternativeText";
						public const string UsagePermissionText = "UsagePermissionText";
					}

					public static class ProductImageVariant
					{
						public const string SizeId = "SizeId";
						public const string Path = "Path";
					}

				}

			}

		}

		#endregion // Inner Classes and Types

		#region Constants

		/// <summary>
		/// The database key to use to create database connections for performing read operations.
		/// </summary>
		protected internal const string DatabaseKeyReadOperations = "ProductImageReadOperations";

		/// <summary>
		/// The schema name to qualify commands with.
		/// </summary>
		protected internal const string DatabaseSchema = "ProductImage";

		#endregion // Constants

		#region Properties

		/// <summary>
		/// Gets or sets the database connection factory to use to create connections to databases.
		/// </summary>
		/// <value>An <see cref="IDatabaseConnectionFactory"/> implementation.</value>
		[Dependency]
		public IDatabaseConnectionFactory DatabaseConnectionFactory { get; set; }

		/// <summary>
		/// Gets or sets the exception handling service to use to handle exceptions.
		/// </summary>
		/// <value>An <see cref="IExceptionHandlingService"/> implementation.</value>
		[Dependency]
		public IExceptionHandlingService ExceptionHandlingService { get; set; }

		#endregion // Properties

		#region IProductImageProvider Members

		/// <summary>
		/// Gets a dictionary of product images by product identifier.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// images are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that
        /// the product whose images are to be retrieved is in.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
		/// whose images are to be retrieved.</param>
		/// <returns>A <see cref="Dictionary{TKey,TValue}"/> of <see cref="ProductImage"/> keyed by
		/// image identifier <see cref="Guid"/>.</returns>
		public Dictionary<Guid, ProductImage> GetProductImages(Guid tenantId, string catalogId, string productId)
		{
			IDbCommand command;
			Dictionary<Guid, ProductImage> images;

			try
			{
				using (IDbConnection connection = CreateDatabaseConnectionReadOperations(tenantId, catalogId, productId))
				{
					connection.Open();
					try
					{
						using (command = CreateGetProductImagesCommand(connection, tenantId, catalogId, productId))
						{
							images = ReadProductImages(command);
						}
						return images;
					}
					finally
					{
						connection.Close();
					}
				}
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductImagesExceptionPolicy.Data);
				throw;
			}
		}

		#endregion // IProductImageProvider Members

		#region Implementation

		/// <summary>
		/// Creates a connection to the database to be used for performing read operations.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant who the
		/// database connection is being created for.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that
        /// the database connection is being created for.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product that
		/// the database connection is being created for.</param>
		/// <returns>An <see cref="IDbConnection"/> connection to the specified database.</returns>
		protected virtual IDbConnection CreateDatabaseConnectionReadOperations(Guid tenantId, string catalogId, string productId)
		{
			return DatabaseConnectionFactory.CreateDatabaseConnection(tenantId, DatabaseKeyReadOperations);
		}

		/// <summary>
		/// Gets the fully-qualified command name.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant who the
		/// command name is being qualified for.</param>
		/// <param name="commandName">The command name to qualify.</param>
		/// <returns>The fully-qualified command name.</returns>
		protected virtual string GetFullyQualifiedCommandName(Guid tenantId, string commandName)
		{
			return String.Concat("[", DatabaseSchema, "].[", commandName, "]");
		}

		private IDbCommand CreateGetProductImagesCommand(IDbConnection connection, Guid tenantId, string catalogId, string productId)
		{
			IDbCommand command;
			IDbDataParameter parameter;

			Safety.AssertArgumentNotNull("connection", connection);
            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			command = connection.CreateCommand();
			command.CommandText = GetFullyQualifiedCommandName(tenantId, StoredProcedure.GetProductImages.CommandName);
			command.CommandType = CommandType.StoredProcedure;

			parameter = command.CreateParameter();
			parameter.ParameterName = StoredProcedure.GetProductImages.ParameterName.TenantId;
			parameter.Value = tenantId;
			command.Parameters.Add(parameter);

            parameter = command.CreateParameter();
            parameter.ParameterName = StoredProcedure.GetProductImages.ParameterName.CatalogId;
            parameter.Value = catalogId;
            command.Parameters.Add(parameter);

            parameter = command.CreateParameter();
			parameter.ParameterName = StoredProcedure.GetProductImages.ParameterName.ProductId;
			parameter.Value = productId;
			command.Parameters.Add(parameter);

			return command;
		}

		private static Dictionary<Guid, ProductImage> ReadProductImages(IDbCommand command)
		{
			Dictionary<Guid, ProductImage> images;

			Safety.AssertArgumentNotNull("command", command);

			images = new Dictionary<Guid, ProductImage>();
			using (IDataReader reader = command.ExecuteReader())
			{
				try
				{
					ReadImages(reader, images);
					Safety.AssertTrue(reader.NextResult(), StoredProcedure.GetProductImages.CommandName + " returned only 1 of the 3 expected result sets.");
					ReadImageLocalizations(reader, images);
					Safety.AssertTrue(reader.NextResult(), StoredProcedure.GetProductImages.CommandName + " returned only 2 of the 3 expected result sets.");
					ReadVariants(reader, images);
					Safety.AssertFalse(reader.NextResult(), StoredProcedure.GetProductImages.CommandName + " returned more than the 3 expected result sets.");
				}
				finally
				{
					reader.Close();
				}
			}
			return images;
		}

		private static void ReadImages(IDataReader reader, Dictionary<Guid, ProductImage> images)
		{
			ProductImage image;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("images", images);

			while (reader.Read())
			{
				image = ReadImage(reader);
				images.Add(image.ImageId, image);
			}
		}

		private static void ReadImageLocalizations(IDataReader reader, Dictionary<Guid, ProductImage> images)
		{
			ProductImageLocalization imageLocalization;
			ProductImage image;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("images", images);

			while (reader.Read())
			{
				imageLocalization = ReadImageLocalization(reader);
				image = images[imageLocalization.ImageId];
				image.Localizations.Add(imageLocalization.CultureId, imageLocalization);
			}
		}

		private static void ReadVariants(IDataReader reader, Dictionary<Guid, ProductImage> images)
		{
			ProductImageVariant variant;
			ProductImage image;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("images", images);

			while (reader.Read())
			{
				variant = ReadVariant(reader);
				image = images[variant.ImageId];
				image.Variants.Add(variant.SizeId, variant);
			}
		}

		private static ProductImage ReadImage(IDataReader reader)
		{
			ProductImage image;

			Safety.AssertArgumentNotNull("reader", reader);

			image = new ProductImage();
			image.ImageId = (Guid)reader[StoredProcedure.GetProductImages.ColumnName.ProductImage.ImageId];
			image.ViewId = (Guid)reader[StoredProcedure.GetProductImages.ColumnName.ProductImage.ViewId];
			image.DefaultAlternativeText = reader[StoredProcedure.GetProductImages.ColumnName.ProductImage.DefaultAlternativeText] as string;
			image.DefaultUsagePermissionText = reader[StoredProcedure.GetProductImages.ColumnName.ProductImage.DefaultUsagePermissionText] as string;
			return image;
		}

		private static ProductImageLocalization ReadImageLocalization(IDataReader reader)
		{
			ProductImageLocalization localization;

			Safety.AssertArgumentNotNull("reader", reader);

			localization = new ProductImageLocalization();
			localization.ImageId = (Guid)reader[StoredProcedure.GetProductImages.ColumnName.ProductImage.ImageId];
			localization.CultureId = (int)reader[StoredProcedure.GetProductImages.ColumnName.ProductImageLocalization.CultureId];
			localization.AlternativeText = reader[StoredProcedure.GetProductImages.ColumnName.ProductImageLocalization.AlternativeText] as string;
			localization.UsagePermissionText = reader[StoredProcedure.GetProductImages.ColumnName.ProductImageLocalization.UsagePermissionText] as string;
			return localization;
		}

		private static ProductImageVariant ReadVariant(IDataReader reader)
		{
			ProductImageVariant variant;

			Safety.AssertArgumentNotNull("reader", reader);

			variant = new ProductImageVariant();
			variant.ImageId = (Guid)reader[StoredProcedure.GetProductImages.ColumnName.ProductImage.ImageId];
			variant.SizeId = (Guid)reader[StoredProcedure.GetProductImages.ColumnName.ProductImageVariant.SizeId];
			variant.Path = (string)reader[StoredProcedure.GetProductImages.ColumnName.ProductImageVariant.Path];
			return variant;
		}

		#endregion // Implementation

	}

}
